﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using PerfAnalyzerLib;
using System.Windows.Forms.DataVisualization.Charting;
using System.IO;

namespace PerfAnalyzerUI
{
	public partial class MainForm : Form
	{
		public static int StatsColumnSize = 120;
		private DataGridViewHelpPerflogExport _gridViewPerflogExport;
		private DataGridViewHelpPerflog _gridViewHelperPerflog;
		private DataGridViewHelpPerflogAnalysis _gridViewHelperPerflogAnalysis;
		delegate void ChanPerflogParsingButtonStatusCallBack(bool enableButtons);

		private Series _selectedSeries = null;

		private Dictionary<Counter, SelectedCounterInfo> SelectedCounters = new Dictionary<Counter, SelectedCounterInfo>();

		internal class SelectedCounterInfo
		{
			public Counter Counter;
			public Series Series;
			public DataGridViewRow Row;

			public SelectedCounterInfo()
			{
			}
		}

		public MainForm()
		{
			InitializeComponent();

			//this.linkLabelWebSite.AutoSize = true;
			//this.linkLabelWebSite.LinkArea = new System.Windows.Forms.LinkArea(0, 20);
			//this.linkLabelWebSite.Location = new System.Drawing.Point(37, 86);
			//this.linkLabelWebSite.Name = "linkLabelWebSite";
			//this.linkLabelWebSite.Size = new System.Drawing.Size(135, 16);
			//this.linkLabelWebSite.TabIndex = 5;
			//this.linkLabelWebSite.TabStop = true;
			//this.linkLabelWebSite.Text = "www.PerfAnalyzer.net";
		}

		private void MainForm_Load(object sender, EventArgs e)
		{
			treeView.AfterCheck += treeView_AfterCheck;

			_gridViewHelperPerflog = new DataGridViewHelpPerflog(dataGridViewPerfLogManager, this);
			_gridViewHelperPerflogAnalysis = new DataGridViewHelpPerflogAnalysis(dataGridViewPerflogAnalysis, this);
			_gridViewPerflogExport = new DataGridViewHelpPerflogExport(dataGridViewPerflogExport, this);

			PerfLogManager.ParsingStatusUpdate += PerfLogManager_ParsingStatusUpdate;
			PerfLogManager.ParsingCompleted += PerfLogManager_ParsingCompleted;

			chart.MouseClick += chart_MouseClick;

			InitializeGraph();
		}

		#region PerflogManager
		void PerfLogManager_ParsingCompleted(object sender, EventArgs e)
		{
			ChangePerflogParsingButtonStatus(true);

			PopulateTreeViewAndOtherControls();

			foreach (var p in PerfLogManager.PerfLogs)
			{
				if (p.Counters.Count > 0)
				{
					_gridViewPerflogExport.AddRow(p);
				}
			}
		}
		
		// TODO add progress bar in status column
		void PerfLogManager_ParsingStatusUpdate(object sender, EventArgs e)
		{
			var ev = (PerfLogManager.PerflogManagerParsingEventArgs)e;
			var perflog = (PerfLog)sender;

			var row = _gridViewHelperPerflog.GetRow(perflog);

			_gridViewHelperPerflog.UpdateStatusColumn(row, ev.PercentParsed.ToString("N0") + "%");
		}

		/// <summary>
		/// Open dialog box to load CSV files.
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		private void buttonAddPerflog_Click(object sender, EventArgs e)
		{
			openFileDialogPerflog.FileOk += openFileDialogPerflog_FileOk;
			openFileDialogPerflog.ShowDialog();
			openFileDialogPerflog.FileOk -= openFileDialogPerflog_FileOk;
		}

		/// <summary>
		/// Called when file are selected.
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		private void openFileDialogPerflog_FileOk(object sender, CancelEventArgs e)
		{
			string[] files = ((OpenFileDialog)sender).FileNames;

			if (null == files || 0 == files.Length)
			{
				return;
			}

			foreach (string file in files)
				AddPerfLog(file);

			dataGridViewPerfLogManager.AutoResizeColumns();
		}

		private void AddPerfLog(string file)
		{
			if (PerfLogManager.IsFileInList(file))
			{
				MessageBox.Show(this, "The file " + file + " is already loaded.", "PerfAnalyzer", MessageBoxButtons.OK, MessageBoxIcon.Information);
				return;
			}

			PerfLog p = PerfLogManager.AddPerfLog(file);
			_gridViewHelperPerflog.AddRow(p);
		}

		private void buttonRemove_Click(object sender, EventArgs e)
		{
			var selectedTags = _gridViewHelperPerflog.GetSelectedTags();

			if (selectedTags.Count  == 0)
			{
				return;
			}

			foreach (var tag in selectedTags)
			{
				_gridViewPerflogExport.RemoveRow(tag);
			}

			_gridViewHelperPerflog.RemoveSelection();

			PopulateTreeViewAndOtherControls();
		}

		private void buttonRemoveAll_Click(object sender, EventArgs e)
		{
			_gridViewPerflogExport.Clear();
			_gridViewHelperPerflog.Clear();
			PopulateTreeViewAndOtherControls();
		}

		private void buttonParse_Click(object sender, EventArgs e)
		{
			if (0 == dataGridViewPerfLogManager.Rows.Count)
			{
				return;
			}

			ChangePerflogParsingButtonStatus(false);
			PerfLogManager.Parse();
		}

		public void ChangePerflogParsingButtonStatus(bool enableButtons)
		{
			if (dataGridViewPerfLogManager.InvokeRequired)
			{
				var d = new ChanPerflogParsingButtonStatusCallBack(ChangePerflogParsingButtonStatus);
				dataGridViewPerfLogManager.Invoke(d, new object[] { enableButtons });
			}
			else
			{
				buttonAddPerflog.Enabled = enableButtons;
				buttonRemove.Enabled = enableButtons;
				buttonRemoveAll.Enabled = enableButtons;
				buttonParse.Enabled = enableButtons;
			}
		}
		#endregion

		#region PerflogAnalysis
		/// <summary>
		/// Event handler for the chart mouse click
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		private void chart_MouseClick(object sender, MouseEventArgs e)
		{
			var pos = e.Location;
			var results = chart.HitTest(pos.X, pos.Y, false, ChartElementType.DataPoint);
			foreach (var result in results)
			{
				if (result.ChartElementType == ChartElementType.DataPoint)
				{
					HighlightSeries(result.Series);
				}
			}
		}

		/// <summary>
		/// Highlight series in the graph.
		/// </summary>
		/// <param name="series"></param>
		private void HighlightSeries(Series series)
		{
			ClearHighlihtedCounter();
			//result.Series.Color = Color.Yellow;
			_selectedSeries = series;
			_selectedSeries.BorderWidth = 3;
			//_gridViewHelperPerflogAnalysis.GetRow(_selectedSeries.Tag).Selected = true;
			_gridViewHelperPerflogAnalysis.SelectAndFocus(_selectedSeries.Tag);
		}

		/// <summary>
		/// Clear selected counter in the chart and gridview
		/// </summary>
		private void ClearHighlihtedCounter()
		{
			if (null != _selectedSeries)
			{
				_selectedSeries.BorderWidth = 1;
				_selectedSeries.Color = _gridViewHelperPerflogAnalysis.GetRowColor(_selectedSeries.Tag);
				_selectedSeries = null;
			}

			_gridViewHelperPerflogAnalysis.ClearSelection();
		}

		/// <summary>
		/// Initialyze graph.
		/// </summary>
		private void InitializeGraph()
		{
			chart.Series.Clear();
			_selectedSeries = null;

			chart.ChartAreas[0].AxisX.MajorGrid.Enabled = false;
			chart.ChartAreas[0].AxisY.MajorGrid.LineColor = Color.LightGray;
			chart.ChartAreas[0].AxisX.LabelStyle.Format = "hh:mm:ss tt";
			chart.ChartAreas[0].AxisX.LabelAutoFitStyle = LabelAutoFitStyles.WordWrap;
		}

		/// <summary>
		/// Add counter to graph.
		/// </summary>
		/// <param name="counter">Counter.</param>
		/// <param name="color">Series color.</param>
		/// <returns></returns>
		private Series AddCounterToGraph(Counter counter, Color color)
		{
			Series series1 = new Series();
			series1.ChartType = SeriesChartType.Line;
			//series1.XValueType = ChartValueType.DateTime;
			//series1.YValueType = ChartValueType.Double;
			for (int i = 0; i < counter.Values.Length; i++)
			{
				series1.Points.AddXY(counter.Perflog.Ticks[i], counter.Values[i]);
			}

			series1.Tag = counter;
			series1.IsVisibleInLegend = false;
			series1.XValueType = ChartValueType.DateTime;
			series1.Color = color;

			chart.Series.Add(series1);

			return series1;
		}

		/// <summary>
		/// Remove counter from graph.
		/// </summary>
		/// <param name="counterTag"></param>
		private void RemoveCounterFromGraph(object counterTag)
		{
			// Could be faster with a TagToSeries List
			foreach (var series in chart.Series)
			{
				if (series.Tag == counterTag)
				{
					chart.Series.Remove(series);

					if (series == _selectedSeries)
					{
						_selectedSeries = null;
					}

					break;
				}
			}
		}

		/// <summary>
		/// Populate the tree view with counters from the parsed perflogs.
		/// </summary>
		private void PopulateTreeViewAndOtherControls()
		{
			chart.Series.Clear();
			_gridViewHelperPerflogAnalysis.Clear();
			treeView.Nodes.Clear();

			foreach (var pl in PerfLogManager.PerfLogs)
			{
				foreach (var counter in pl.Counters)
				{
					AddTreeNode(counter);
				}
			}

			treeView.Sort();
		}

		/// <summary>
		/// Add node in tree view for the given counter.
		/// </summary>
		/// <param name="counter"></param>
		private void AddTreeNode(Counter counter)
		{
			TreeNode current;

			// TODO check with path

			// category
			if (!treeView.Nodes.ContainsKey(counter.Info.Category))
			{
				treeView.Nodes.Add(counter.Info.Category, counter.Info.Category);
			}
			current = treeView.Nodes[counter.Info.Category];

			// instance
			if (!string.IsNullOrEmpty(counter.Info.Instance))
			{
				// has instance
				if (!current.Nodes.ContainsKey(counter.Info.Instance))
				{
					current.Nodes.Add(counter.Info.Instance, counter.Info.Instance);
				}

				current = current.Nodes[counter.Info.Instance];
			}

			// counter
			if (!current.Nodes.ContainsKey(counter.Info.Counter))
			{
				current.Nodes.Add(counter.Info.Counter, counter.Info.Counter);
			}
			current = current.Nodes[counter.Info.Counter];

			// machine
			if (!current.Nodes.ContainsKey(counter.Info.Machine))
			{
				current.Nodes.Add(counter.Info.Machine, counter.Info.Machine);
			}
			current = current.Nodes[counter.Info.Machine];

			// logfile
			var tn = new TreeNode(counter.Perflog.Filename);
			current.Nodes.Add(tn);
			tn.Tag = counter;
		}

		/// <summary>
		/// Event handler for the tree view checklist
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		void treeView_AfterCheck(object sender, TreeViewEventArgs e)
		{
			// The code only executes if the user caused the checked state to change. 
			if (e.Action != TreeViewAction.Unknown)
			{
				CheckAllChildNodes(e.Node, e.Node.Checked);
				CheckParentNodes(e.Node, e.Node.Checked);
				dataGridViewPerflogAnalysis.Refresh();
			}

			ClearHighlihtedCounter();
		}

		/// <summary>
		/// Un/Check parents node.
		/// If another child of the node's parent is checked, do no change the parents.
		/// </summary>
		/// <param name="childNode"></param>
		/// <param name="nodeChecked"></param>
		private void CheckParentNodes(TreeNode childNode, bool nodeChecked)
		{
			TreeNode parentNode = childNode.Parent;

			if (parentNode == null) 
			{
				return;
			}

			if (!nodeChecked)
			{
				// check for other parent's node child selected
				foreach (TreeNode node in parentNode.Nodes)
				{
					if (node != childNode && node.Checked)
					{
						return;
					}
				}
			}

			parentNode.Checked = nodeChecked;
			CheckParentNodes(parentNode, nodeChecked);
		}

		/// <summary>
		/// Check all the 
		/// </summary>
		/// <param name="treeNode"></param>
		/// <param name="nodeChecked"></param>
		private void CheckAllChildNodes(TreeNode treeNode, bool nodeChecked)
		{
			if (treeNode.Tag != null)
			{
				if (nodeChecked)
				{
					AddCounterToSelection((Counter)treeNode.Tag);
				}
				else
				{
					RemoveCounterFromSelection((Counter)treeNode.Tag);
				}
			}

			// If the current node has child nodes, call the CheckAllChildsNodes method recursively. 
			foreach (TreeNode node in treeNode.Nodes)
			{
				node.Checked = nodeChecked;
				CheckAllChildNodes(node, nodeChecked);
			}
		}

		/// <summary>
		/// Add counter to list of selected counter.
		/// </summary>
		/// <param name="counter">Counter to add to selection.</param>
		private void AddCounterToSelection(Counter counter)
		{
			SelectedCounterInfo counterInfo = new SelectedCounterInfo();
			counterInfo.Row = _gridViewHelperPerflogAnalysis.AddRow(counter);
			counterInfo.Series = AddCounterToGraph(counter,  ColorManagement.Next());
			counterInfo.Counter = counter;

			_gridViewHelperPerflogAnalysis.SetRowColor(counter, ColorManagement.Current());

			SelectedCounters.Add(counter, counterInfo);
		}

		/// <summary>
		/// Remove given counter from selected counters.
		/// </summary>
		/// <param name="counter">Counter to remove from selection.</param>
		private void RemoveCounterFromSelection(Counter counter)
		{
			_gridViewHelperPerflogAnalysis.RemoveRowWithTag(counter);
			RemoveCounterFromGraph(counter);
			SelectedCounters.Remove(counter);
		}

		/// <summary>
		/// Event handler for the click on the data grid view of the Analysis tab.
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		private void dataGridViewPerflogAnalysis_CellClick(object sender, DataGridViewCellEventArgs e)
		{
			var row = dataGridViewPerflogAnalysis.Rows[e.RowIndex];
			var counter = (Counter)row.Tag;

			HighlightSeries(SelectedCounters[counter].Series);
		}
		#endregion

		#region Export
		private void buttonExport_Click(object sender, EventArgs e)
		{
			if (dataGridViewPerflogExport.Rows.Count == 0)
			{
				return;
			}

			saveFileDialogExport.FileOk += saveFileDialogExport_FileOk;
			saveFileDialogExport.ShowDialog();
			saveFileDialogExport.FileOk -= saveFileDialogExport_FileOk;
		}

		private void saveFileDialogExport_FileOk(object sender, CancelEventArgs e)
		{
			string file = ((SaveFileDialog)sender).FileName;

			if (File.Exists(file))
			{
				File.Delete(file);
			}

			var selectedLogs = new List<PerfLog>();

			foreach (DataGridViewRow row in dataGridViewPerflogExport.Rows)
			{
				DataGridViewCheckBoxCell cell = (DataGridViewCheckBoxCell)row.Cells[DataGridViewHelpPerflogExport.ColSelection];

				if ((bool)cell.Value)
				{
					selectedLogs.Add((PerfLog)row.Tag);
				}
			}

			PerfLogManager.Export(file, selectedLogs);
		}
		#endregion

		/// <summary>
		/// Event handler for the website link.
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		private void linkLabelWebSite_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e)
		{
			System.Diagnostics.Process.Start("http://www.perfanalyzer.net");
		}
	}
}
 